package no.ntnu.fp.su;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Collections;

import no.ntnu.fp.db.Database;
import no.ntnu.fp.mmi.GraphingDataModel;

/**
 * Log Object to contain all the {@link LogEntry} objects that belong to a well
 * 
 * @author abs
 * 
 */
public class Log {

	private ArrayList<LogEntry> logEntries;
	private int writtenEntries;
	final static public int DB_WRITE_BUFFER_TIME = 5;

	private Filter filter;
	private Database db;
	private int wellID;
	private boolean read;

	private Timestamp lastUpdateTime;
	private Timestamp oldestEntry;

	/**
	 * create a new log
	 * 
	 * @param {@link Database} the database of the parent well
	 * @param {@link Integer} wellID of the parent well
	 * 
	 */
	public Log(Database db, int wellID) {
		logEntries = new ArrayList<LogEntry>();
		read = false;
		this.wellID = wellID;
		filter = Filter.ALL;
		this.db = db;
	}

	/**
	 * Retrieve the log, based on input Filter
	 * 
	 * @param {@link Filter}
	 * 
	 * @return {@link ArrayList<LogEntry>}
	 */
	public ArrayList<LogEntry> getLog(Filter filter) {
		if (filter == Filter.ALL) {
			return logEntries;
		}
		ArrayList<LogEntry> log = new ArrayList<LogEntry>();
		for (LogEntry l : logEntries) {
			if (Filter.valueOf(l.getStatus()) == filter) {
				log.add(l);
			}
		}
		return log;
	}

	/**
	 * Retrieve the log, based on the selected enum
	 * 
	 * @return {@link ArrayList<LogEntry>}
	 */
	public ArrayList<LogEntry> getLog() {
		if (filter == Filter.ALL) {
			return logEntries;
		}
		ArrayList<LogEntry> log = new ArrayList<LogEntry>();
		for (LogEntry l : logEntries) {
			if (Filter.valueOf(l.getStatus()) == filter) {
				log.add(l);
			}
		}
		return log;
	}

	/**
	 * Get the latest/newest {@link LogEntry} in this log
	 * 
	 * @return {@link LogEntry}
	 */
	public LogEntry getLatest() {
		if (logEntries.size() < 1)
			return null;
		return logEntries.get(logEntries.size() - 1);
	}

	/**
	 * 
	 * add the Entry to the Log
	 * 
	 * @param {@link LogEntry} The LogEntry
	 */
	public void addEntry(LogEntry entry) {
		if (!read)
			return;
		logEntries.add(entry);
		entry.addDB(this.db);

		if (writtenEntries + DB_WRITE_BUFFER_TIME < logEntries.size())
			writeEntriesToDB();
	}

	/**
	 * Write new entries to the {@link Database}
	 */
	private synchronized void writeEntriesToDB() {
		db.connect();

		int tmp = logEntries.size();
		for (LogEntry entry : logEntries.subList(writtenEntries, tmp)) {
			entry.writeToDb(db);
		}
		writtenEntries = tmp;

		db.disconnect();

	}

	/**
	 * set startup values for the Log to know what to write to the
	 * {@link Database} later
	 */
	public void updateFromDBinit() {
		if (!read)
			throw new NullPointerException("LES");
		if (logEntries.size() != 0)
			lastUpdateTime = logEntries.get(logEntries.size() - 1).getTime();
		else
			lastUpdateTime = new Timestamp(System.currentTimeMillis());
	}

	/**
	 * Update the log, only used by Master-WATCH
	 */
	public void updateFromDB() {
		db.connect();
		ResultSet rs = db.query("select * from welllog where time > \""
				+ lastUpdateTime.toString() + "\" and wellid=" + wellID
				+ " order by time desc limit " + GraphingDataModel.GRAPHSIZE);

		try {
			ArrayList<LogEntry> les = new ArrayList<LogEntry>();
			while (rs.next()) {
				LogEntry le = new LogEntry(rs.getInt(1), Status.valueOf(rs
						.getObject(7).toString()), rs.getDouble(3), rs
						.getDouble(4), rs.getDouble(5), rs.getDouble(6), rs
						.getTimestamp(2));

				les.add(le);
				le.addDB(db);
			}
			Collections.reverse(les);

			logEntries.addAll(les);
			// logEntries.add(le);
		} catch (SQLException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

	/**
	 * Reads inn Log from wellLog database into the ArrayList
	 * 
	 * @param {@link Integer} number of minutes back in time to load from
	 *        database
	 */
	public void readLogFromDb(int buffer) {
		if (read) {
			System.out.println("log already read");
			return;
		}
		db.connect();
		LogEntry l;

		oldestEntry = new Timestamp(System.currentTimeMillis()
				- (buffer * 60000));
		String q = "select * from welllog where time > \""
				+ (new Timestamp(System.currentTimeMillis() - (buffer * 60000))
						.toString()) + "\" and wellid=" + wellID
				+ " order by time";

		ResultSet rs = db.query(q);

		System.out.println(q);

		try {
			while (rs.next()) {
				l = new LogEntry(rs.getInt(1), Status.valueOf(rs.getObject(7)
						.toString()), rs.getDouble(3), rs.getDouble(4), rs
						.getDouble(5), rs.getDouble(6), rs.getTimestamp(2));
				logEntries.add(l);
				l.addDB(this.db);
				// System.out.println("well " + rs.getInt(1) + " @ "
				// + rs.getTimestamp(2) + " status: "
				// + rs.getObject(7).toString());
			}
			read = true;
		} catch (SQLException e) {
			System.out.println("error when importing log from db");
			e.printStackTrace();
		}
		System.out.println("imported " + logEntries.size()
				+ " logentries from the DB");
		db.disconnect();
		writtenEntries = logEntries.size();

	}

	/**
	 * Reads inn specific log interval from wellLog database into a new
	 * {@link ArraList<LogEntry>}
	 * 
	 * @param {@link Timestamp} start of interval
	 * @param {@link Timestamp} end of interval
	 * @return {@link ArraList<LogEntry>}
	 */
	public Log getLogIntervalFromDB(Timestamp start, Timestamp stop) {
		db.connect();
		Log intervalLog = new Log(this.db, this.wellID);
		LogEntry l;

		String q = "select * from welllog where time > \"" + start.toString()
				+ "\" and time < \"" + stop.toString() + "\" and wellid="
				+ wellID + " order by time";

		ResultSet rs = db.query(q);

		System.out.println(q);

		try {
			while (rs.next()) {
				l = new LogEntry(rs.getInt(1), Status.valueOf(rs.getObject(7)
						.toString()), rs.getDouble(3), rs.getDouble(4), rs
						.getDouble(5), rs.getDouble(6), rs.getTimestamp(2));
				intervalLog.getLog().add(l);
				l.addDB(this.db);
				// System.out.println("well " + rs.getInt(1) + " @ "
				// + rs.getTimestamp(2) + " status: "
				// + rs.getObject(7).toString());
			}
		} catch (SQLException e) {
			System.out.println("error when importing log from db");
			e.printStackTrace();
		}
		System.out.println("fetched " + intervalLog.getLog().size()
				+ " logentries from the DB");
		db.disconnect();
		intervalLog.oldestEntry = start;
		intervalLog.writtenEntries = intervalLog.getLog().size();
		intervalLog.lastUpdateTime = stop;
		return intervalLog;
	}

	/**
	 * set the filter to show in the log
	 * 
	 * @param {@link Filter}
	 */
	public void setFilter(Filter filter) {
		this.filter = filter;
	}

	/**
	 * get the current logFilter
	 * 
	 * @return {@link Filter}
	 */
	public Filter getFilter() {
		return this.filter;
	}

	/**
	 * Read Comments linked to this log
	 */
	public void readComments() {
//		if (logEntries.size() < 1) {
//			throw new NullPointerException(
//					"there are no logentries in this log to read comments from");
//		}
		db.connect();
		String q = "select * from comments where wellid=" + wellID
				+ " and time>\"" + oldestEntry.toString() + "\" and time<\""
				+ lastUpdateTime.toString() + "\"";
		ResultSet rs = db.query(q);
		System.out.println(q);
		int comments = 0;
		boolean newC = true;
		try {
			while (rs.next()) {
				LogEntry le = getlogByTime(rs.getTimestamp(2));
				if (le == null) {
					throw new NullPointerException(
							"no logentry found for comment "
									+ rs.getInt(1)
									+ " "
									+ rs.getInt(3)
									+ " "
									+ rs.getTimestamp(2)
									+ " \noldest "
									+ this.logEntries.get(0).getTime()
											.toString()
									+ " \nnewest "
									+ logEntries.get(logEntries.size() - 1)
											.getTime().toString() + " size "
									+ logEntries.size());
				}
				// for (LogEntry le : logEntries) {
				// if (le.getTime().getTime() / 1000 == rs.getTimestamp(2)
				// .getTime() / 1000) {
				newC = true;
				for (Comment c : le.getComments()) {
					if (c.getCommentID() == rs.getInt(3)) {
						newC = false;
					}
				}
				if (newC) {
					le.addComment(rs.getInt(3), rs.getString(4));
					comments++;
				}

				// }
				// }
			}
		} catch (SQLException e) {
			System.out.println("error loading comments");
			e.printStackTrace();
		}
		db.disconnect();
		System.out.println("comments found: " + comments);
	}

	/**
	 * Search for a {@link LogEntry} in the log based on time
	 * 
	 * @param {@link Timestamp} Time of the {@link LogEntry}
	 * @return {@link LogEntry}, or {@link null} if none was found
	 */
	public LogEntry getlogByTime(Timestamp timeStamp) {
		int low = 0;
		int high = logEntries.size() - 1;
		int mid;

		String t = timeStamp.toString().substring(0, 19);

		while (low <= high) {
			mid = (low + high) / 2;
			String midL = logEntries.get(mid).getTime().toString().substring(0,
					19);
			// System.out.println("current " + midL);
			// System.out.println("lookin for " + t);
			if (midL.compareTo(t) < 0)
				low = mid + 1;
			else if (midL.compareTo(t.toString()) > 0)
				high = mid - 1;
			else
				return logEntries.get(mid);
		}

		return null; // NOT_FOUND = -1
	}

	/**
	 * Compare two {@link Timestamp}s, regardless of millisecond accuracy, the
	 * DB uses 0 decimals, java uses 3
	 * 
	 * @param {@link Timestamp} 1
	 * @param {@link Timestamp} 2
	 * @return <br>
	 *         >0 if t1>t2 <br>
	 *         <0 if t2>t1 <br>
	 *         0 if t1==t2
	 */
	public static int compareTime(Timestamp t1, Timestamp t2) {
		return t1.toString().substring(0, 19).compareTo(
				t2.toString().substring(0, 19));
	}
}
